# 学习看见系列的支持函数
# Welch实验室

import numpy as np  # 导入numpy库
import matplotlib.colors as mcolors  # 导入matplotlib.colors模块中的mcolors
from PIL import Image  # 从PIL库中导入Image模块
import matplotlib.pyplot as plt  # 导入matplotlib.pyplot模块中的plt

# 从imageDict中提取9x9网格和手指/非手指标签
def extractFeatures(imageDict, whichImage='image1bit', dist=4):  # 定义提取特征的函数，参数包括imageDict、whichImage和dist
    img = imageDict[whichImage]  # 从imageDict中获取whichImage对应的图像数据

    featuresList = []  # 初始化特征列表
    target = np.zeros(imageDict['numPointsInBox'])  # 创建一个全零数组作为目标值
    counter = 0  # 初始化计数器

    for i in range(imageDict['boxEdges'][2], imageDict['boxEdges'][3]):  # 遍历图像的行
        for j in range(imageDict['boxEdges'][0], imageDict['boxEdges'][1]):  # 遍历图像的列
            f = img[i-dist:i+dist+1, j-dist:j+dist+1]  # 从图像中提取9x9的子图像

            fVec = f.ravel()  # 将子图像展平为一维数组
            featuresList.append(fVec)  # 将展平后的子图像添加到特征列表中

            # 检查该像素是否为手指像素
            if np.max(np.sum(imageDict['allFingerPoints'] == [i, j], axis=1)) == 2:  # 判断该像素是否为手指像素
                target[counter] = 1  # 如果是手指像素，则将目标值设置为1

            counter = counter + 1  # 计数器加一
            
    features = np.vstack(featuresList)  # 将特征列表转换为数组并返回
    
    return features, target  # 返回特征和目标值

# 从图像字典列表中提取单个示例：
def extractExamplesFromList(imageList, whichImage='image1bit', dist=4):
    allFeaturesList = []  # 初始化特征列表
    allTargetList = []  # 初始化目标值列表

    for i, imageDict in enumerate(imageList):  # 遍历图像字典列表
        features, target = extractFeatures(imageDict, whichImage=whichImage, dist=dist)  # 调用extractFeatures函数提取特征和目标值
        allFeaturesList.append(features)  # 将特征添加到特征列表中
        allTargetList.append(target)  # 将目标值添加到目标值列表中

    x = np.vstack((allFeaturesList))  # 将特征列表垂直堆叠为特征数组
    y = np.hstack((allTargetList))  # 将目标值列表水平堆叠为目标值数组

    return x, y  # 返回特征数组和目标值数组


# 定义一个函数，用于查找并计数数组中的唯一行
def findUniqueRowsAndCount(a):
    # 将数组转换为连续的内存块，并将其视图更改为一维void类型
    b = np.ascontiguousarray(a).view(np.dtype((np.void, a.dtype.itemsize * a.shape[1])))
    # 使用numpy的unique函数找到唯一的行，并返回它们的索引和计数
    _, idx, counts = np.unique(b, return_index=True, return_counts = True)
    
    # 使用索引从原始数组中提取唯一的行，并将其转换为整数类型
    aUnique = a[idx].astype('int')
    
    # 返回唯一的行，它们的索引和计数
    return aUnique, idx, counts

# 导入matplotlib的颜色模块
import matplotlib.colors as mcolors

# 定义一个函数，用于创建一个线性分段的颜色映射
def make_colormap(seq):
    """Return a LinearSegmentedColormap
    seq: a sequence of floats and RGB-tuples. The floats should be increasing
    and in the interval (0,1).
    """
    # 创建一个新的序列，其中包含原始序列的元素，以及在开始和结束时添加的元素
    seq = [(None,) * 3, 0.0] + list(seq) + [1.0, (None,) * 3]
    # 初始化颜色字典
    cdict = {'red': [], 'green': [], 'blue': []}
    # 遍历新的序列
    for i, item in enumerate(seq):
        # 如果当前元素是一个浮点数
        if isinstance(item, float):
            # 获取前一个和后一个元素的RGB值
            r1, g1, b1 = seq[i - 1]
            r2, g2, b2 = seq[i + 1]
            # 将这些RGB值添加到颜色字典中
            cdict['red'].append([item, r1, r2])
            cdict['green'].append([item, g1, g2])
            cdict['blue'].append([item, b1, b2])
    # 使用颜色字典创建一个线性分段的颜色映射，并返回它
    return mcolors.LinearSegmentedColormap('CustomMap', cdict)

# 定义一个函数，用于将图像转换为灰度图像
def makeGrayScale(imageDict):
    # 创建一个新的全零数组，其形状与图像的高度和宽度相同，但是有3个通道
    im = np.zeros((imageDict['boxHeight'], imageDict['boxWidth'], 3))
    # 对于每个通道，将图像的像素值除以255，以将它们转换为0-1的范围
    im[:,:,0] = 1./255*imageDict['image'][imageDict['boxEdges'][2]:imageDict['boxEdges'][3], \
                                       imageDict['boxEdges'][0]:imageDict['boxEdges'][1]] 
    im[:,:,1] = 1./255*imageDict['image'][imageDict['boxEdges'][2]:imageDict['boxEdges'][3], \
                                       imageDict['boxEdges'][0]:imageDict['boxEdges'][1]]
    im[:,:,2] = 1./255*imageDict['image'][imageDict['boxEdges'][2]:imageDict['boxEdges'][3], \
                                       imageDict['boxEdges'][0]:imageDict['boxEdges'][1]]
    
    # 返回灰度图像
    return im

from PIL import Image  # 导入PIL库的Image模块，用于进行图像处理

# 定义一个函数，用于计算混淆矩阵和相关的指标
def computeConfusionMatrix(y, yHat, verbose = True):
    
    # 确保输入的数据是一维的，而不是二维的numpy数组
    if y.ndim == 2:
        y = y[:,0]
        
    if yHat.ndim == 2:
        yHat = yHat[:,0]

    # 计算假阴性（FN）的数量
    FN =  np.sum(np.logical_and(y==1, yHat==0))
    # 计算假阳性（FP）的数量
    FP =  np.sum(np.logical_and(y==0, yHat==1))
    # 计算真阳性（TP）的数量
    TP = np.sum(np.logical_and(y==1, yHat==1))
    # 计算真阴性（TN）的数量
    TN = np.sum(np.logical_and(y==0, yHat==0))
    
    # 创建混淆矩阵
    cm = np.array([[TP, FN], [FP, TN]])
    
    # 如果TP和FN的和不为0，计算召回率（recall）
    if (TP + FN != 0):
        recall = float(TP)/(TP+FN)
    else:
        recall = 0
        
    # 如果TP和FP的和不为0，计算精确度（precision）
    if (TP+FP != 0):
        precision = float(TP)/(TP+FP)
    else:
        precision = 0
    # 计算准确度（accuracy）
    accuracy = float(TP+TN)/len(y)
    
    # 如果verbose为True，打印混淆矩阵和相关的指标
    if verbose:
        print('Confusion Matrix:')
        print(cm)
        print('Recall (TPR) = ' + str(round(recall,3))  + \
            ' (Portion of fingers that we "caught")')
        print('Precision (PPV) = ' + str(round(precision,3)) + \
            '(Portion of predicted finger pixels that were actually finger pixels)')
        print('Accuracy = ' + str(round(accuracy,3)))
    
    # 返回混淆矩阵和相关的指标
    return cm, accuracy, recall, precision

# 导入matplotlib的颜色模块
import matplotlib.colors as mcolors

# 创建一个颜色转换器，用于将颜色名称转换为RGB值
c = mcolors.ColorConverter().to_rgb

# 创建一个蓝白色的颜色映射，其中蓝色的部分占据了2/3的范围
bw = make_colormap([(1,1,1), (1,1,1), 0.33, c('blue'), c('blue'), 0.66, c('blue')])

# 创建一个红白色的颜色映射，其中红色的部分占据了2/3的范围
rw = make_colormap([(1,1,1), (1,1,1), 0.33, c('red'), c('red'), 0.66, c('red')])

# 创建一个红白蓝色的颜色映射，其中红色和蓝色各占据了1/3的范围，白色占据了中间的1/3的范围
rwb = make_colormap([c('red'), c('red'), 0.33, (1,1,1), (1,1,1), 0.66, c('blue')])


def testRules(rules, exampleIndices, data, fig, X, y, showLegend = True, color = 'Full'):
    ## 颜色选项: 全部，绿色.
    
    # 遍历每个示例索引
    for i in range(len(exampleIndices)):
        # 在图形上添加一个子图
        ax = fig.add_subplot(1,4,i+1)
        # 从数据中获取对应索引的图像字典
        imageDict = data[exampleIndices[i]]

        # 提取特征和目标值
        X1, y1 = extractFeatures(imageDict, whichImage = 'image1bit', dist = 4)
        # 将图像转换为灰度图
        im = makeGrayScale(imageDict)

        # 初始化匹配索引的数组
        matchingIndices = np.array([], dtype = 'int')
        
        # 如果规则是列表类型
        if type(rules) is list:
            # 遍历每个规则
            for rule in rules:
                # 计算特征和规则的差异
                diff = X1 - rule.ravel()
                # 找出与规则匹配的索引
                mI = np.where(~diff.any(axis=1))[0]
                # 将匹配的索引添加到匹配索引的数组中
                matchingIndices = np.concatenate((matchingIndices, mI))
        
        # 如果规则是numpy数组类型
        elif type(rules) is np.ndarray:
            # 遍历每个规则
            for i in range(rules.shape[0]):
                # 计算特征和规则的差异
                diff = X1 - rules[i, :]
                # 找出与规则匹配的索引
                mI = np.where(~diff.any(axis=1))[0]
                # 将匹配的索引添加到匹配索引的数组中
                matchingIndices = np.concatenate((matchingIndices, mI))
            
        # 如果规则是函数类型
        elif callable(rules):
            # 找出满足规则的索引
            matchingIndices = np.where(rules(X1))[0]
          
        matchVec = np.zeros(X1.shape[0])
        # 创建一个全零向量

        matchVec[matchingIndices] = 1
        # 将匹配的索引处的值设置为1

        truePositives = np.logical_and(y1, matchVec)
        # 计算真阳性的值

        falsePositives = np.logical_and(np.logical_not(y1), matchVec)
        # 计算假阳性的值

        falseNegatives = np.logical_and(y1, np.logical_not(matchVec))
        # 计算假阴性的值

        if color == 'Full':
            fNImage = falseNegatives.reshape(imageDict['boxHeight'], imageDict['boxWidth'])
            # 将假阴性的值重塑为图像的形状

            # 使用匹配的值对图像进行着色
            im[:,:,0][fNImage==1] = 1
            im[:,:,1][fNImage==1] = 1
            im[:,:,2][fNImage==1] = 0

            tPImage = truePositives.reshape((imageDict['boxHeight'], imageDict['boxWidth']))
            # 将真阳性的值重塑为图像的形状

            # 使用匹配的值对图像进行着色
            im[:,:,0][tPImage==1] = 0
            im[:,:,1][tPImage==1] = 1
            im[:,:,2][tPImage==1] = 0

            fNImage = falsePositives.reshape((imageDict['boxHeight'], imageDict['boxWidth']))
            # 将假阳性的值重塑为图像的形状

            # 使用匹配的值对图像进行着色
            im[:,:,0][fNImage==1] = 1
            im[:,:,1][fNImage==1] = 0
            im[:,:,2][fNImage==1] = 0

        if color == 'Green':
            matchImage = matchVec.reshape((imageDict['boxHeight'], imageDict['boxWidth']))
            # 将匹配的值重塑为图像的形状

            # 使用匹配的值对图像进行着色
            im[:,:,0][matchImage==1] = 0
            im[:,:,1][matchImage==1] = 1
            im[:,:,2][matchImage==1] = 0

        ax.imshow(im, interpolation = 'none')
        # 在子图上显示图像

        ax.axis('off')
        # 关闭坐标轴
               
    if showLegend:
        legend = Image.open('images/legendOne.png', 'r')
        # 如果需要显示图例，就打开图例图片

        ax4 = fig.add_subplot(1,4,len(exampleIndices)+1)
        # 在图形上添加一个新的子图

        ax4.imshow(legend)
        # 在新的子图上显示图例图片

        ax4.axis('off')
        # 关闭坐标轴

    # 现在，在所有数据中搜索匹配项：
    matchingIndices = np.array([], dtype = 'int')
    # 初始化一个空的匹配索引数组

    # 如果规则是列表类型
    if type(rules) is list:
        for rule in rules:
            # 对于每个规则，计算特征和规则的差异
            diff = X - rule.ravel()
            # 找出与规则匹配的索引
            mI = np.where(~diff.any(axis=1))[0]
            # 将匹配的索引添加到匹配索引的数组中
            matchingIndices = np.concatenate((matchingIndices, mI))

    # 如果规则是numpy数组类型
    elif type(rules) is np.ndarray:
        for i in range(rules.shape[0]):
            # 对于每个规则，计算特征和规则的差异
            diff = X - rules[i, :]
            # 找出与规则匹配的索引
            mI = np.where(~diff.any(axis=1))[0]
            # 将匹配的索引添加到匹配索引的数组中
            matchingIndices = np.concatenate((matchingIndices, mI))

    # 如果规则是函数类型
    elif callable(rules):
        # 找出满足规则的索引
        matchingIndices = np.where(rules(X))[0]

    yHat = np.zeros(X.shape[0])
    # 创建一个全零向量

    yHat[matchingIndices] = 1
    # 将匹配的索引处的值设置为1

    cm, accuracy, recall, precision = computeConfusionMatrix(y, yHat, verbose = True)
    # 计算混淆矩阵和各种评估指标 

########### -------------- Quasi Deprecated Functions ------------------------ ########
# 显示简单规则的匹配项：
def showMatches(rules, exampleIndices, data, fig, verbose = True):
    # 定义一个函数，参数包括规则、示例索引、数据、图形和是否显示详细信息

    for i in range(len(exampleIndices)):
        # 对于每个示例索引
        ax = fig.add_subplot(1,len(exampleIndices),i+1)
        # 在图形上添加一个新的子图

        imageDict = data[exampleIndices[i]]
        # 获取当前示例的数据

        X, y = extractFeatures(imageDict, whichImage = 'image1bit', dist = 4)
        # 提取特征

        im = makeGrayScale(imageDict)
        # 将图像转换为灰度图

        matchingIndices = np.array([], dtype = 'int')
        # 初始化一个空的匹配索引数组

        for rule in rules:
            # 对于每个规则
            difference = X - rule.ravel()
            # 计算特征和规则的差异

            mI = np.where(~difference.any(axis=1))[0]
            # 找出与规则匹配的索引

            matchingIndices = np.concatenate((matchingIndices, mI))
            # 将匹配的索引添加到匹配索引的数组中

        matchVec = np.zeros(X.shape[0])
        # 创建一个全零向量

        matchVec[matchingIndices] = 1
        # 将匹配的索引处的值设置为1

        matchImage = matchVec.reshape((imageDict['boxHeight'], imageDict['boxWidth']))
        # 将匹配的值重塑为图像的形状

        # 使用匹配的值对图像进行着色
        im[:,:,0][matchImage==1] = 0
        im[:,:,1][matchImage==1] = 1
        im[:,:,2][matchImage==1] = 0

        ax.imshow(im, interpolation = 'none')
        # 在子图上显示图像

        ax.axis('off')
        # 关闭坐标轴

        if verbose:
            plt.title('Number of Matches = ' + str(sum(matchVec ==1)), fontsize = 14)
            # 如果需要显示详细信息，就添加标题

def testLogicalRules(exampleIndices, data, fig, X, y, rule):
    # 定义一个函数，参数包括示例索引、数据、图形、特征、标签和规则

    legend = Image.open('images/legendOne.png', 'r')
    # 打开图例图片

    for i in range(len(exampleIndices)):
        # 对于每个示例索引
        ax = fig.add_subplot(1,4,i+1)
        # 在图形上添加一个新的子图

        imageDict = data[exampleIndices[i]]
        # 获取当前示例的数据

        X1, y1 = extractFeatures(imageDict, whichImage = 'image1bit', dist = 4)
        # 提取特征

        im = makeGrayScale(imageDict)
        # 将图像转换为灰度图

        yImage = y1.reshape(imageDict['boxHeight'], imageDict['boxWidth'])
        # 将标签重塑为图像的形状

        # 使用标签对图像进行着色
        im[:,:,0][yImage==1] = 1
        im[:,:,1][yImage==1] = 1
        im[:,:,2][yImage==1] = 0

        yHat = rule(X1)
        # 根据规则预测标签

        truePositives = np.logical_and(y1, yHat)
        # 计算真阳性

        falsePositives = np.logical_and(np.logical_not(y1), yHat)
        # 计算假阳性

        tPImage = truePositives.reshape((imageDict['boxHeight'], imageDict['boxWidth']))
        # 将真阳性重塑为图像的形状

        # 使用真阳性对图像进行着色
        im[:,:,0][tPImage==1] = 0
        im[:,:,1][tPImage==1] = 1
        im[:,:,2][tPImage==1] = 0

        fNImage = falsePositives.reshape((imageDict['boxHeight'], imageDict['boxWidth']))
        # 将假阳性重塑为图像的形状

        # 使用假阳性对图像进行着色
        im[:,:,0][fNImage==1] = 1
        im[:,:,1][fNImage==1] = 0
        im[:,:,2][fNImage==1] = 0

        ax.imshow(im, interpolation = 'none')
        # 在子图上显示图像

        ax.axis('off')
        # 关闭坐标轴

    ax4 = fig.add_subplot(1,4,len(exampleIndices)+1)
    # 在图形上添加一个新的子图
    ax4.imshow(legend)
    # 在新的子图上显示图例图片
    ax4.axis('off')
    # 关闭坐标轴
    yHat = rule(X)
    # 根据规则预测所有特征的标签
    cm, accuracy, recall, precision = computeConfusionMatrix(y, yHat, verbose = True)
    # 计算混淆矩阵和各种评估指标
    
############ ------------- 完全弃用的函数 ----------------- #########

# def testRules(rules, exampleIndices, data, fig, X, y):
#     legend = Image.open('legendOne.png', 'r')

#     for i in range(len(exampleIndices)):
#         ax = fig.add_subplot(1,4,i+1)
#         imageDict = data[exampleIndices[i]]

#         X1, y1 = extractFeatures(imageDict, whichImage='image1bit', dist=4)

#         im = makeGrayScale(imageDict)
#         yImage = y1.reshape(imageDict['boxHeight'], imageDict['boxWidth'])

#         # 使用真实标签绘制图像：
#         im[:,:,0][yImage==1] = 1
#         im[:,:,1][yImage==1] = 1
#         im[:,:,2][yImage==1] = 0

#         matchingIndices = np.array([], dtype='int')
#         for rule in rules:
#             diff = X1 - rule.ravel()
#             mI = np.where(~diff.any(axis=1))[0]
#             matchingIndices = np.concatenate((matchingIndices, mI))

#         matchVec = np.zeros(X1.shape[0])
#         matchVec[matchingIndices] = 1

#         truePositives = np.logical_and(y1, matchVec)
#         falsePositives = np.logical_and(np.logical_not(y1), matchVec)

#         tPImage = truePositives.reshape((imageDict['boxHeight'], imageDict['boxWidth']))

#         # 使用匹配结果绘制图像：
#         im[:,:,0][tPImage==1] = 0
#         im[:,:,1][tPImage==1] = 1
#         im[:,:,2][tPImage==1] = 0

#         fNImage = falsePositives.reshape((imageDict['boxHeight'], imageDict['boxWidth']))

#         # 使用匹配结果绘制图像：
#         im[:,:,0][fNImage==1] = 1
#         im[:,:,1][fNImage==1] = 0
#         im[:,:,2][fNImage==1] = 0

#         ax.imshow(im, interpolation='none')
#         ax.axis('off')

#     ax4 = fig.add_subplot(1,4,len(exampleIndices)+1)
#     ax4.imshow(legend)
#     ax4.axis('off');

#     # 现在，在所有数据中查找匹配项：
#     matchingIndices = np.array([], dtype='int')

#     for rule in rules:
#         diff = X - rule.ravel()
#         mI = np.where(~diff.any(axis=1))[0]
#         matchingIndices = np.concatenate((matchingIndices, mI))
        
#     yHat = np.zeros(X.shape[0])
#     yHat[matchingIndices] = 1

#     cm, accuracy, recall, precision = computeConfusionMatrix(y, yHat, verbose=True)
